package org.fjsei.yewu.controller;

//import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import md.specialEqp.type.PipingUnit;
//import org.elasticsearch.action.search.*;
import org.elasticsearch.client.RequestOptions;
//import org.elasticsearch.core.TimeValue;
//import org.elasticsearch.index.query.BoolQueryBuilder;
//import org.elasticsearch.index.query.TermsSetQueryBuilder;
//import org.elasticsearch.search.aggregations.AggregationBuilders;
//import org.elasticsearch.search.aggregations.Aggregations;
//import org.elasticsearch.search.aggregations.bucket.terms.Terms;
//import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
//import org.elasticsearch.search.aggregations.metrics.Cardinality;
//import org.elasticsearch.search.aggregations.metrics.CardinalityAggregationBuilder;
//import org.elasticsearch.search.builder.PointInTimeBuilder;
//import org.elasticsearch.search.builder.SearchSourceBuilder;
//import org.elasticsearch.search.collapse.CollapseBuilder;
//import org.elasticsearch.search.sort.SortBuilders;
//import org.elasticsearch.search.sort.SortOrder;
//import org.fjsei.yewu.dto.SearchRequest;
import org.fjsei.yewu.entity.incp.Jcdunit;
import org.fjsei.yewu.index.CompanyEs;
import org.fjsei.yewu.index.EqpEs;
import org.fjsei.yewu.jpa.PageOffsetFirst;
import org.fjsei.yewu.service.SearchService;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.core.*;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery;
//import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
//import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import jakarta.validation.constraints.NotBlank;
import java.util.*;
import java.util.stream.StreamSupport;

//import org.elasticsearch.index.query.*;
//import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
//import static org.elasticsearch.index.query.QueryBuilders.matchPhraseQuery;


@Slf4j
@RestController
@RequestMapping("services")
public class SearchController {
    private static final int  SEARCH_AFTER_PAGE_SIZE = 30;

//    @Autowired
//    private ElasticsearchRestTemplate esTemplate;
    @Autowired
    private ElasticsearchOperations esTemplate;
    //Hibernate search的：
    @Autowired
    private SearchService searchService;
    /**自动从URL提取注入的Pageable数据;  例子 /services/search?q=江镜&page=499&size=20&sort=id&sort=no,desc
     * springmvc的请求中自动的根据request的参数来组装该pageable对象，Spring支持的request参数如下：
     * public Page<blog> listByPageable( @PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC)  Pageable page) {};
     * page，第几页，从0开始，默认为第0页
     * size，每一页的大小，默认为20
     * sort，排序相关的信息，以property,property(,ASC|DESC)的方式组织，例如&sort=firstname&sort=lastname,desc表示在按firstname正序排列基础上按lastname倒序排列。
     * REST模式缺陷=带来的？ 最后一步return response会陷入死循环！ 前端竟然得到这样的应答
     * */
    @GetMapping("search")
    public org.fjsei.yewu.dto.SearchResponse<PipingUnit> search(@RequestParam("q") String query, Pageable page) {
        org.fjsei.yewu.dto.SearchRequest request = new org.fjsei.yewu.dto.SearchRequest(query, null, null, page);
        //SearchResponse<? extends Eqp> response = searchService.searchQuery(request);
        org.fjsei.yewu.dto.SearchResponse<PipingUnit> response = searchService.searchQuery(request);
        //标记成@FullTextField(analyzer = "ik_smart") ?q=江镜&page=0&size=20能查到17条， 去掉ik_smart设置，默认竟能查出629条的。
        return response;        //可能报错的，JPA session?关联对象没获取到。
    }

    /**手动更新索引,直接用http client工具,一直等到结束才回答，超时也没关系，看日志继续运作：可能要先删除ES索引.
     PUT http://192.168.171.3:8673/services/reindexHsEs
     Content-Type: application/json
     Cookie: token=eyJhbGciOAVg

     {
     "index": "Person,Adminunit"
     }
     问题太慢！实体提取太多无关字段了。  https://docs.jboss.org/hibernate/search/6.2/reference/en-US/html_single/
     严重受制于数据库以及IO性能。 考虑简化？
     * */
    @PutMapping("/reindexHsEs")
    public Boolean reindexES(@Validated @RequestBody LikeIndexParams likeIndexParams) {
        log.info("手动更新索引, param: {}", likeIndexParams);
        String[] names= likeIndexParams.getIndex().split(",");
        Boolean ok = searchService.doMassIndex(names);
        log.info("手动更新索引:{};result: {}", likeIndexParams.getIndex(), ok);
        return ok;
    }
    @GetMapping("allcount")
    public Iterable<EqpEs> deeallcount(@RequestParam("q") String query, Pageable page) throws JSONException {
        Pageable pageable;
        pageable = PageOffsetFirst.of(0, 10, Sort.by(Sort.Direction.ASC , "_id"));
//        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(
//                boolQuery().must(
//                        matchPhraseQuery("ust", "")
//                )
//        ).withPageable(pageable).build();
//        IndexCoordinates indexCoordinates = esTemplate.getIndexCoordinatesFor(EqpEs.class);
//        SearchHits<EqpEs> searchHits = esTemplate.search(searchQuery, EqpEs.class, indexCoordinates);

        SearchHits<EqpEs> searchHits = esTemplate.search(
                NativeQuery.builder().withQuery(
                        builder ->
                            builder.matchPhrase(
                                    f->f.field("ust").query("")
                            )

                ).withPageable(pageable).build(),
                EqpEs.class
        );

        List<SearchHit<EqpEs>> hits = searchHits.getSearchHits();
        Iterable<EqpEs> list = (List<EqpEs>) SearchHitSupport.unwrapSearchHits(hits);
//        String sql = searchQuery.getQuery().toString();
        return list;
    }
    /**测试ES全表轮询，深度翻页，整个索引全量导出类似; search after前面启动页*/
    @GetMapping("deepPage0")
    public Iterable<EqpEs> deepPaged0(@RequestParam("q") String query, Pageable page) throws JSONException {
        Pageable pageable;
        pageable = PageOffsetFirst.of(0, 10, Sort.by(Sort.Direction.ASC , "_id"));
//        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(
//                boolQuery().must(
//                        matchPhraseQuery("sort", "31").slop(7)
//                )
//        ).withPageable(pageable).build();
//        IndexCoordinates indexCoordinates = esTemplate.getIndexCoordinatesFor(EqpEs.class);
//        SearchHits<EqpEs> searchHits = esTemplate.search(searchQuery, EqpEs.class, indexCoordinates);

        SearchHits<EqpEs> searchHits = esTemplate.search(
                NativeQuery.builder().withQuery(
                        builder ->
                                builder.matchPhrase(
                                        f->f.field("sort").query("31").slop(7)
                                )
                ).withPageable(pageable).build(),
                EqpEs.class
        );
        List<SearchHit<EqpEs>> hits = searchHits.getSearchHits();
        //利用到上一次的｛初始化时第一次的｝查询最后一个数据
        SearchHit result = hits.get(hits.size() - 1);
        String  lastSort= JSON.toJSONString(result.getSortValues());
        Iterable<EqpEs> list = (List<EqpEs>) SearchHitSupport.unwrapSearchHits(hits);
//        String sql = searchQuery.getQuery().toString();
        return list;
    }
    /**最好的深度分页方式， 参看 https://www.shardik.com/blog/2021/03/09/es-deep-search/
     * ?? https://stackoverflow.com/questions/58383527/spring-boot-elasticsearch-search-after ;
     * 分页的4种方式 PIT   https://www.pudn.com/news/624978048947fd5953a670ff.html
     GET http://192.168.171.3:8673/services/deepPage?q=江镜&page=499&size=20&sort=id&sort=no,desc
     Content-Type: application/json
     * */
    @GetMapping("deepPage")
    public Iterable<EqpEs> deepPaged(@RequestParam("q") String query, Pageable page) throws JSONException {
        Pageable pageable;
        pageable = PageOffsetFirst.of(0, 10, Sort.by(Sort.Direction.ASC , "_id"));
        String lastSort= "[\"0001ec32-2028-420c-8e7b-4484a975f410\"]";      //上一次的sorts[]
        //第二次开始的查询才会有searchAfter()构造器参数，第一次初始化时通用from+size分页类似的查询语句;
        //存储上一次分页的sort信息    searchSourceBuilder2.searchAfter(new List[]{上次result[p-1].getSortValues()});
        List<Object> afterObj= JSON.parseObject(lastSort, List.class);
//        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(
//                boolQuery().must(
//                        matchPhraseQuery("sort", "31").slop(7)
//                )
//        ).withPageable(pageable).withSearchAfter(afterObj).build();
//        IndexCoordinates indexCoordinates = esTemplate.getIndexCoordinatesFor(EqpEs.class);
//        SearchHits<EqpEs> searchHits = esTemplate.search(searchQuery, EqpEs.class, indexCoordinates);

        SearchHits<EqpEs> searchHits = esTemplate.search(
                NativeQuery.builder().withQuery(
                        builder ->  builder.matchPhrase(
                                        f->f.field("sort").query("31").slop(7)
                                )
                ).withPageable(pageable).withSearchAfter(afterObj).build(),
                EqpEs.class
        );

        List<SearchHit<EqpEs>> hits = searchHits.getSearchHits();
        Iterable<EqpEs> list = (List<EqpEs>) SearchHitSupport.unwrapSearchHits(hits);
//        String sql = searchQuery.getQuery().toString();
        return list;
    }

    //ES7.0版本时期的测试：【统计】Elasticsearch聚合 各种形式 nested集合字段[]  https://blog.csdn.net/qq_24950043/article/details/125013357
    @Deprecated
    @GetMapping("groupBy")
    public Iterable<EqpEs> groupByTest(@RequestParam("q") String query, Pageable page) throws JSONException {
//            String aggName = "status_bucket";
//            NativeSearchQueryBuilder queryBuilder =  new NativeSearchQueryBuilder();
//            queryBuilder.withPageable(PageRequest.of(0,3000));
//            TermsAggregationBuilder termsAgg = AggregationBuilders.terms(aggName).field("useu.id");
//            queryBuilder.withAggregations(termsAgg).withMaxResults(10000);
//
//            IndexCoordinates indexCoordinates = esTemplate.getIndexCoordinatesFor(EqpEs.class);
//            //【问题】对于聚合，queryBuilder.?.?.字段可以动态修改： 最大默认65536, 超过报错limit can be set by changing the [search.max_buckets] cluster level setting
//            Aggregations aggregations = (Aggregations) esTemplate.search(queryBuilder.build(),EqpEs.class).getAggregations().aggregations();
//            Terms terms = aggregations.get(aggName);
//            //SearchResponse::getAggregations); .get(aggName)
//            List<? extends Terms.Bucket> buckets = terms.getBuckets();
//            HashMap<String,Long> statusRes = new HashMap<>();
//            buckets.forEach(bucket -> {
//                statusRes.put(bucket.getKeyAsString(),bucket.getDocCount());
//            });
//            System.out.println("---聚合结果---");
//            System.out.println(statusRes);
        return null;
    }
    //字段折叠Collapse=特殊的聚合， 去重分页  较高成本的 PIT（point in time）分页   https://cdn.modb.pro/db/437328
    @Deprecated
    @GetMapping("collapse")
    public Iterable<EqpEs> collapseTest(@RequestParam("q") String query, Pageable page) throws JSONException {
//        // 指定基数聚合字段为 random， 名字为 random_count
//        final CardinalityAggregationBuilder cardinalityAggregation = AggregationBuilders.cardinality("random_count").field("useu.id");
//        //【限制】报错Result window is too large, from + size must be less than or equal to: [10000]
//        final SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource()
//                .from(0).size(10000)       //collapse && search after分页(sort(针对字段组合) 只能单一个字段的 &&需collapse的同一字段)
//                // 折叠
//                .collapse(new CollapseBuilder("useu.id"))
//                // 聚合
//                .aggregation(cardinalityAggregation)
//                // 排序
//                .sort(SortBuilders.fieldSort("useu.id").order(SortOrder.ASC));
//
//            SearchRequest searchRequest = new SearchRequest();
//            final String indexname="ee3-read,ev2-read,ec4-read,ep8-read,ea6-read,eb1-read,ef5-read,er9-read,eqp-read";
//            searchRequest.indices(indexname);
//            searchRequest.source(sourceBuilder);
//            SearchResponse searchResponse = esTemplate.execute(
//                    client -> client.search(searchRequest, RequestOptions.DEFAULT)
//            );
//            int size = searchResponse.getHits().getHits().length;
//
//    // 获取 聚合 结果，这里是 1000
//            final Cardinality randomCount = searchResponse.getAggregations().get("random_count");
//            final long randomCountValue = randomCount.getValue();
//        //    final SearchRequest searchRequest = new SearchRequest();
//        //    searchRequest.indices("test-paginate-index");
//        //    searchRequest.source(sourceBuilder);
//        //    SearchResponse response = highLevelClient.search(searchRequest);
        return null;
    }
    //ES试验：新版本分页的Point In Time 快照游标模式的测试： 不适合web无连接场景用，适合有连接的客户端使用。
    @Deprecated
    @GetMapping("pitPage")
    public Iterable<EqpEs> pitPageTest(@RequestParam("q") String query, Pageable page) throws JSONException {
//            final String indexname="ee3-read,ev2-read,ec4-read,ep8-read,ea6-read,eb1-read,ef5-read,er9-read,eqp-read";
//            final OpenPointInTimeRequest pitRequest = new OpenPointInTimeRequest(indexname);
//            pitRequest.keepAlive(TimeValue.timeValueMinutes(10));
//        //【初始化采用的】 打开 pit 获取 pitId
//            final OpenPointInTimeResponse pitResponse = esTemplate.execute(
//                    client -> client.openPointInTime(pitRequest, RequestOptions.DEFAULT)
//            );
//            final String pitId = pitResponse.getPointInTimeId();
//        //PIT结合 search after 或者 from size: 分页
//            final PointInTimeBuilder pitBuilder = new PointInTimeBuilder(pitId);
//            pitBuilder.setKeepAlive(TimeValue.timeValueMinutes(10));
//            final SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource()
//                    .pointInTimeBuilder(pitBuilder) // 指定 pit
//                    .from(9990).size(10)
//                    .sort(SortBuilders.fieldSort("useu.id").order(SortOrder.ASC));
//             // .searchAfter(new Object[]{10?,,?});  结合 search after
//
//            SearchRequest searchRequest = new SearchRequest();
//            searchRequest.source(searchSourceBuilder);
//            final SearchResponse searchResponse = esTemplate.execute(
//                    client -> client.search(searchRequest, RequestOptions.DEFAULT)
//            );
//        //【模拟】下面紧接着 另外1个分页页面：
//            final SearchSourceBuilder searchSourceBuilder2 = SearchSourceBuilder.searchSource()
//                    .pointInTimeBuilder(pitBuilder) // 指定 pit
//                    .from(0).size(10)
//                    .sort(SortBuilders.fieldSort("useu.id").order(SortOrder.ASC))
//                    .searchAfter(new Object[]{ "2e379088-8c4b-47c9-8eb2-793a162b2c7f" });   // 结合 search after
//            SearchRequest searchRequest2 = new SearchRequest();
//            //.searchAfter() .from(10)报错"`from` parameter must be set to 0 when `search_after` is used.
//            //照错 " Result window is too large, from + size must be less than or equal to: [10000]
//            searchRequest2.source(searchSourceBuilder2);
//            final SearchResponse searchResponse2 = esTemplate.execute(
//                    client -> client.search(searchRequest2, RequestOptions.DEFAULT)
//            );
//
//        //【分页全部完成】 关闭 Point In Time 必须手动删除！
//            final ClosePointInTimeRequest closePointInTimeRequest = new ClosePointInTimeRequest(pitId);
//            esTemplate.execute(
//                    client -> client.closePointInTime(closePointInTimeRequest,RequestOptions.DEFAULT)
//            );
            return null;
    }

    //【仅仅测试】下面的四个接口；  测试新版ES8
    @RequestMapping(value = "/searchES8", method=RequestMethod.GET)
    public ResponseEntity<List<CompanyEs>> search(@RequestParam String q) {
        System.out.println(q);
        return new ResponseEntity<>(
                searchService.searchResults(q), HttpStatus.OK
        );
    }
//    @RequestMapping(value = "/get/{id}", method=RequestMethod.GET)
//    public ResponseEntity<FooEntity> get(@PathVariable String id) {
//        return new ResponseEntity<>(
//                repo.findById(id).get(),
//                HttpStatus.OK
//        );
//    }

//    @RequestMapping(value = "/", method=RequestMethod.POST)
//    public ResponseEntity<FooEntity> post(@RequestBody FooEntity foo) {
//        repo.save(foo);
//        return new ResponseEntity<>(
//                foo, HttpStatus.CREATED
//        );
//    }

//    @RequestMapping(value = "/list", method=RequestMethod.GET)
//    public ResponseEntity<List<FooEntity>> list(@RequestParam List<String> ids) {
//        List<FooEntity> results = StreamSupport.stream(
//                repo.findAllById(ids).spliterator(), false
//        ).toList();
//        return new ResponseEntity<>(
//                results, HttpStatus.CREATED
//        );
//    }

}


@Data
class LikeIndexParams {
    /** 业务KEY
     */
    @NotBlank
    private String index;
}


/*REST: search(Adminunit)报错； City. Set<Adminunit>  ads; Rest也会自动提取关联的内省对象：
failed to lazily initialize a collection of role: md.cm.geography.City.ads, could not initialize proxy - no Session
或报错 java.lang.StackOverflowError: null；
* */
